1. Object Oriented Programming and Arrays

To read more about OOP, read Chapter 6. To read more about Arrays, read Chapter 5.

This week, we are working with a robot. The robot is actually a new type of Java thing, called a class. We make it do things by calling functions using a dot, like:

robot.forward(1);

In this exploration, we examine the idea of an object, and begin writing our first object-oriented programming study.

1.1 Old Ball Code

This code is from the discussion on Bouncing Balls. We need to keep track of the balls' velocities in the x and y direction, keep track of where it is (x, y), and the global time.

Here we have two balls. If we wanted more, we would have to have global variables for each ball.

In [1]:
float g = 9.8; 
float dt;  
float t;

float vx1;
float vy1;
float x1;
float y1;

float vx2;
float vy2;
float x2;
float y2;

void setup() {
    size(200, 500);
    dt = 0.1;  
    t = 0;
    x1 = width/2;
    y1 = 50;
    vx1 = 50.0;
    vy1 = 0.0;
    x2 = width/2;
    y2 = 25;
    vx2 = -50.0;
    vy2 = -10.0;
}

void drawBall(float x, float y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, w, h);
}

void draw() {
    background(200);
    // gravity
    vy1 = vy1 + g * dt;
    
    float dx = vx1 * dt;    
    if (((x1 + dx) > width) || ((x1 + dx) < 0)) {
        vx1 = vx1 * -0.8;
    } else {
        x1 = x1 + dx;
    }

    vy2 = vy2 + g * dt;
    
    dx = vx2 * dt;    
    if (((x2 + dx) > width) || ((x2 + dx) < 0)) {
        vx2 = vx2 * -0.8;
    } else {
        x2 = x2 + dx;
    }

    
    float dy = vy1 * dt;
    if (((y1 + dy) > height) || ((y1 + dy) < 0)) {
        vy1 = vy1 * -0.8;
    } else {
        y1 = y1 + dy;
    }

    dy = vy2 * dt;
    if (((y2 + dy) > height) || ((y2 + dy) < 0)) {
        vy2 = vy2 * -0.8;
    } else {
        y2 = y2 + dy;
    }

    drawBall(x1, y1, 10, 10);
    drawBall(x2, y2, 15, 15);
    
    t = t + dt;
}
Sketch #1:

Sketch #1 state: Loading...

1.2 Goal: Organize the Chaos

Our goal is to move this code into a new structure called a class (or type).

This comes in two parts:

  1. the definition of the class/type
  2. the creation of an instance of one of these new types

Part 1

class CLASSNAME {
    TYPE VARIABLE [= VALUE]; // sometimes called "attributes" (optional)
    ...

    CLASSNAME() { // called the "constructor"
        // if you need to refer to yourself, use "this."
    }

    TYPE FUNCTION(PARAMETER, ...) { // functions, called "methods" (optional)
    }
}

Note:

  • word "class"
  • indented
  • functions are now called "methods"
  • special function called the "constructor"
    • has no return type
    • has the same name as the class

Part 2

CLASSNAME VARIABLE = new CLASSNAME();

Note:

  • just like any other type
  • use the keyword "new" followed by calling the constructor

1.2.1 Examples

In [20]:
class Ball {
    Ball() {
        println("Created a ball!");
    }
    void speak() {
        println("I am a ball!");
    }
}

Ball bouncing_ball = new Ball();

void draw() {
    //bouncing_ball.speak();
    noLoop();
}
Sketch #18:

Sketch #18 state: Loading...
Created a ball!
Created a ball!
In [22]:
class Ball {
    String name;
    
    Ball(String n) {
        this.name = n;
    }
    void speak() {
        println("I am " + this.name + "!");
    }
}

Ball bouncing_ball_1 = new Ball("Steve");
Ball bouncing_ball_2 = new Ball("Doug");

void draw() {
    bouncing_ball_1.speak();
    bouncing_ball_2.speak();
    noLoop();
}
Sketch #20:

Sketch #20 state: Loading...
I am Steve!
I am Doug!

1.3 Transitioning to Classes and Instances

What if we wanted to add more balls? We would need to add variables for vx, vy, x, y... for each ball!

Better way: classes!

  1. start with above code
  2. trim code down to one ball
  3. create a class definition
  4. move globals into constructor
  5. move functions into class; now called "methods"
In [30]:
// original code to be changed in class to a new Ball class

float g = 9.8; 
float dt;  
float t;

class Ball {
    float vx;
    float vy;
    float x;
    float y;
    float w = 10;
    float h = 10;

    Ball(float x, float y, float vx, float vy) { //, float w, float h) {
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
        //this.w = w;
        //this.h = h;
    }
    
    void drawBall() {
        fill(255, 0, 0);
        ellipse(x, y, w, h);
    }

    void move() {
        // gravity
        vy = vy + g * dt;

        float dx = vx * dt;    
        if (((x + dx) > width) || ((x + dx) < 0)) {
            vx = vx * -0.8;
        } else {
            x = x + dx;
        }


        float dy = vy * dt;
        if (((y + dy) > height) || ((y + dy) < 0)) {
            vy = vy * -0.8;
        } else {
            y = y + dy;
        }

        drawBall();    
    }
        
}

Ball redball = new Ball(width/2, 50, 50, 0);
Ball redball2 = new Ball(width/4, 25, 10, -5);

void setup() {
    size(200, 500);
    dt = 0.1;  
    t = 0;
    //x = width/2;
    //y = 50;
    //vx = 50.0;
    //vy = 0.0;
}

void draw() {
    background(200);
    redball.move();
    redball2.move();
    t = t + dt;
}
Sketch #25:

Sketch #25 state: Loading...

1.4 Finished Product

In [6]:
float g = 9.8; 
float dt;  
float t;

class Ball {
    float x;
    float y;
    float vx;
    float vy;
    float w;
    float h;
    
    Ball() {
        x = width/2;
        y = 50;
        vx = 50.0;
        vy = 0.0;
        w = 10;
        h = 10;
    }
    
    void move() {
        vy = vy + g * dt;

        float dx = vx * dt;    
        if (((x + dx) > width) || ((x + dx) < 0)) {
            vx = vx * -0.8;
        } else {
            x = x + dx;
        }
        float dy = vy * dt;
        if (((y + dy) > height) || ((y + dy) < 0)) {
            vy = vy * -0.8;
        } else {
            y = y + dy;
        }
    }

    void draw() {
        fill(255, 0, 0);
        ellipse(x, y, w, h);
    }    
}

Ball pluto = new Ball();

void setup() {
    size(200, 500);
    dt = 0.1;  
    t = 0;
}

void draw() {
    background(200);
    t = t + dt;
    pluto.move();
    pluto.draw();
}
Sketch #5:

Sketch #5 state: Loading...

1.5 Bouncing Ball as an Object

A better way is to let each ball keep track of its own properties. We do this by making a class definition, and storing each set of variables inside the Ball object.

Here we will move all of the ball-specific variables "into the Ball class" and leave the others.

Four steps for turning regular code into Object-Oriented code:

  1. Define a Ball "object" using the word class
  2. Move all global variables dealing with ball inside class
  3. Move all functions inside class. These are then called methods
    1. There is a special method of the same name of the class called the constructor
    2. Use the word this to represent the current object
  4. Create an instance of the object using the work new

We define a class, create an instance of a Ball, and "bounce" it.

In [7]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball ball; // the ball

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    // Jargon: we create an "instance" of the Ball class:
    ball = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
    t = 0;
}

void draw() {
    background(200);
    ball.moveYourself();
    ball.drawYourself();
    
    t = t + dt;
}
Sketch #6:

Sketch #6 state: Loading...

If we wanted to have two (or more) we need only create more instances:

In [8]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball ball0; // the balls
Ball ball1; 

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    // Jargon: we create an "instance" of the Ball class:
    ball0 = new Ball(width/2, 50, 10, 10, 50.0, 20.0);
    ball1 = new Ball(width/4, 50, 10, 10, -29.0, -5.0);
    t = 0;
}

void draw() {
    background(200);
    ball0.moveYourself();
    ball0.drawYourself();
    
    ball1.moveYourself();
    ball1.drawYourself();
    
    t = t + dt;
}
Sketch #7:

Sketch #7 state: Loading...

What if we wanted 10? Or 1,000?

1.6 Introducing Arrays

To allow a large number of items, we use arrays.

Using an array takes three steps:

  1. We use Ball[] balls; as the type to define the array
  2. We need to create space for each ball: balls = new Ball[1];
  3. We then create the instances of each ball in the array: new Ball(...);

To read more about Arrays, read Chapter 5.

In [9]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball[] balls; // the balls

void setup() {
    size(200, 500);
    balls = new Ball[1];
    // Jargon: we create an "instance" of the Ball class:
    balls[0] = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
    t = 0;
}

void draw() {
    background(200);
    balls[0].moveYourself();
    balls[0].drawYourself();
    
    t = t + dt;
}

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}
Sketch #8:

Sketch #8 state: Loading...

It is fairly easy to add more balls:

  1. Create more space in the array
  2. Create more instances
  3. Make sure we call move and update for each ball
In [10]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball[] balls; // the balls

void setup() {
    size(200, 500);
    balls = new Ball[2];
    // Jargon: we create an "instance" of the Ball class:
    balls[0] = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
    balls[1] = new Ball(width/4, 50, 10, 10, 50.0, 0.0);
    t = 0;
}

void draw() {
    background(200);
    balls[0].moveYourself();
    balls[0].drawYourself();

    balls[1].moveYourself();
    balls[1].drawYourself();
    
    t = t + dt;
}

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}
Sketch #9:

Sketch #9 state: Loading...

1.7 Properties of Arrays

It can be even easier!

Make code simpler:

  • Arrays have the property .length
  • Use for-loop to move and draw all of the balls
In [31]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball[] balls; // the balls

void setup() {
    size(200, 500);
    balls = new Ball[200];
    // Jargon: we create an "instance" of the Ball class:
    for (int i = 0; i < balls.length; i++) {
        balls[i] = new Ball(width * random(1), height * random(1), 10, 10, random(50) - 25, 0.0);
    }
    t = 0;
}

void draw() {
    background(128);
    for (int i = 0; i < balls.length; i++) {
        balls[i].moveYourself();
        balls[i].drawYourself();
    }
    t = t + dt;
}

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;
    color c;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
        this.c = color(random(255), random(255), random(255));
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(this.c);
        ellipse(this.x, this.y, this.w, this.h);
    }
}
Sketch #26:

Sketch #26 state: Loading...

1.8 Variations

Cut and paste the above code and try some variations:

  1. More than one ball
  2. Add color to the balls
  3. Add a ball where you click the mouse

1.9 Collision Detection

It would be cool if the balls could detect when they run into each other, just like it detects when it hits a wall. To do that, we go through the other balls, and identify when they "overlap".

Then, we simply swap their velocities.

In [13]:
// Globals:

float g = 9.8;  // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball [] balls; // the balls

float distance(float x1, float y1, float x2, float y2) {
    return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
}

class Ball {
    color c;
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    Ball(color c, float x, float y, int w, int h, float vx, float vy) {
        this.c = c;
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    void move() {
        for (int i = 0; i < balls.length; i++) {
            if (balls[i] != this) {
                if (distance(this.x, this.y, balls[i].x, balls[i].y) < (this.w/2 + balls[i].w/2)) {
                    float temp = this.vx;
                    this.vx = balls[i].vx;
                    balls[i].vx = temp;
                    temp = this.vy;
                    this.vy = balls[i].vy;
                    balls[i].vy = temp;
                    break;
                }
            }
        }
        this.vy = this.vy + g * dt;      
        float dx = this.vx * dt;    
        float dy = this.vy * dt;
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void draw() {
        fill(this.c);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    balls = new Ball[10];
    for (int i = 0; i < balls.length; i++) {
        balls[i] = new Ball(color(random(255), random(255), random(255)), random(width), random(40), 10, 10, random(50), 0.0);
    }
    t = 0;
}

void draw() {
    background(128);
    for (int i = 0; i < balls.length; i++) {
        balls[i].move();
        balls[i].draw();
    }
    t = t + dt;
}
Sketch #12:

Sketch #12 state: Loading...

Variations:

  1. Create different kinds of balls (e.g., different shapes... just use your previous drawObject here)
  2. Make the Physics more realistic (e.g., add some loss of energy, randomness)
  3. Create them where you click the mouse.
  4. Take into account the balls' masses (e.g., like this
  5. Have some things in the environment that don't move (gravity should not effect them, and they have no velocities)
  6. Have some locations that inject velocities (e.g., shoot objects into the air)
In [18]:
// Globals:

float g = 9.8;  // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball [] balls; // the balls

float distance(float x1, float y1, float x2, float y2) {
    return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
}

class Ball {
    color c;
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    Ball(color c, float x, float y, int w, int h, float vx, float vy) {
        this.c = c;
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    void move() {
        for (int i = 0; i < COUNT; i++) {
            if (balls[i] != this) {
                if (distance(this.x, this.y, balls[i].x, balls[i].y) < (this.w/2 + balls[i].w/2)) {
                    float temp = this.vx;
                    this.vx = balls[i].vx;
                    balls[i].vx = temp;

                    temp = this.vy;
                    this.vy = balls[i].vy;
                    balls[i].vy = temp;
                    break;
                }
            }
        }
        this.vy = this.vy + g * dt;      
        float dx = this.vx * dt;    
        float dy = this.vy * dt;
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void draw() {
        fill(this.c);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    balls = new Ball[100];
    COUNT = 0;
    t = 0;
}

int COUNT = 0;

void mousePressed() {
    balls[COUNT] = new Ball(color(random(255), random(255), random(255)), 
                            mouseX, mouseY, 10, 10, random(50) - 25, 0);
    COUNT++;
}

void draw() {
    background(128);
    for (int i = 0; i < COUNT; i++) {
        balls[i].move();
        balls[i].draw();
    }    
    t = t + dt;
}
Sketch #16:

Sketch #16 state: Loading...
I am a ball!
I am Steve!
I am Steve!
I am Doug!